#include "Loader.h"

#include <iostream>

#include "assimp/scene.h"
#include "assimp/mesh.h"
#include "assimp/material.h"
#include "assimp/Importer.hpp"
#include "assimp/postprocess.h"

#include "FileUtil.h"
#include "Node.h"
#include "Mesh.h"
#include "Geometry.h"
using namespace Assimp;
renderengine::Loader::Loader()
{

}

renderengine::Loader::~Loader()
{
}

std::shared_ptr<renderengine::Node> renderengine::Loader::load(std::string filename)
{
    if (!FileUtil::exists(filename))
    {
        std::cout << filename << " not exists!" << std::endl;
        return false;
    }
    std::string fileExt = FileUtil::getFileExt(filename);
    Importer importer;
    auto rawScene = importer.ReadFile(filename, aiProcess_Triangulate |
        aiProcess_JoinIdenticalVertices |
        aiProcess_OptimizeMeshes | aiProcess_FindInstances |
        aiProcess_OptimizeGraph);
    
    if (rawScene) {
        std::shared_ptr<Node> scene = std::make_shared<Node>();
        loadScene(rawScene, scene);
        return scene;
    }
    else {
        return nullptr;
    }
}

void renderengine::Loader::loadScene(const aiScene * rawScene, std::shared_ptr<Node> node)
{
    loadNode(rawScene, rawScene->mRootNode, node);
}

void renderengine::Loader::loadNode(const aiScene * rawScene, const aiNode * rawNode, std::shared_ptr<Node> node)
{
    //if (node->getParent()) {
    //    setTransformation(rawNode, node);
    //}
    loadMesh(rawScene, rawNode,  node);
    loadNodeChildren(rawScene, rawNode,  node);
}

void renderengine::Loader::loadMesh(const aiScene * rawScene, const aiNode * rawNode, std::shared_ptr<Node> node)
{
    for (unsigned int i = 0; i < rawNode->mNumMeshes; ++i) {
        loadMesh(rawScene, rawNode->mMeshes[i],  node);
    }
}

void renderengine::Loader::loadMesh(const aiScene * rawScene, unsigned int meshIndex, std::shared_ptr<Node> node)
{
    auto childNode = std::make_shared<Node>();
    node->addChild(childNode);
    loadGeometry(rawScene, meshIndex, childNode);
    loadMaterial(rawScene, rawScene->mMeshes[meshIndex]->mMaterialIndex,  childNode);
}

void renderengine::Loader::loadGeometry(const aiScene * rawScene, unsigned int meshIndex, std::shared_ptr<Node> node)
{
    auto geometryIter = mGeometries.find(meshIndex);
    if (geometryIter == mGeometries.end()) {
        auto geometry = createGeometry(rawScene->mMeshes[meshIndex]);
        if (geometry) {
            //node->setGlobalId(Util::createUUID(0));
            //node->setName(rawScene->mMeshes[meshIndex]->mName.C_Str());
            //scene->addObject(geometry);
            node->addGeometry(geometry);
            mGeometries[meshIndex] = geometry;
        }
    }
    else {
        node->addGeometry(geometryIter->second);
    }
}

void renderengine::Loader::loadMaterial(const aiScene * rawScene, unsigned int materialIndex, std::shared_ptr<Node> node)
{
}

void renderengine::Loader::loadTextures(const aiMaterial * rawMaterial, std::shared_ptr<Node> node, std::shared_ptr<Material> material)
{
}

void renderengine::Loader::loadNodeChildren(const aiScene * rawScene, const aiNode * rawNode, std::shared_ptr<Node> node)
{
}

void renderengine::Loader::setTransformation(const aiNode * rawNode, std::shared_ptr<Node> node)
{
}

std::shared_ptr<renderengine::Geometry> renderengine::Loader::createGeometry(const aiMesh * rawMesh)
{
    std::shared_ptr<Geometry> geometry;
    switch (rawMesh->mPrimitiveTypes) {
    case aiPrimitiveType_POINT:
        geometry = createMultiPoint(rawMesh);
        break;
    case aiPrimitiveType_LINE:
        geometry = createMultiLine(rawMesh);
        break;
    case aiPrimitiveType_TRIANGLE:
        geometry = createMesh(rawMesh);
        break;
    case aiPrimitiveType_POLYGON:
        break;
    default:
        break;
    }
    return geometry;
}

std::shared_ptr<renderengine::Geometry> renderengine::Loader::createMesh(const aiMesh * rawMesh)
{
    Mesh* mesh = new Mesh();

    std::vector<glm::vec3> position;
    aiVector3D *point = rawMesh->mVertices;
    for (int i = 0; i < rawMesh->mNumVertices; i++)
    {
        position.push_back(glm::vec3(point[i][0], point[i][1], point[i][2]));
    }
    mesh->setPosition(position);

    std::vector<glm::vec3> normal;
    aiVector3D *nor = rawMesh->mNormals;
    for (int i = 0; i < rawMesh->mNumVertices; i++)
    {
        normal.push_back(glm::vec3(nor[i][0], nor[i][1], nor[i][2]));
    }
    mesh->setNormal(normal);

    std::vector<glm::vec2> texcoord;
    aiVector3D * coord = rawMesh->mTextureCoords[0];
    for (int i = 0; i < rawMesh->mNumVertices; i++)
    {
        texcoord.push_back(glm::vec2(coord[i][0], coord[i][1]));
    }
    mesh->setTexcoord(texcoord);

    // to do index

    return std::shared_ptr<Geometry>(mesh);
}

std::shared_ptr<renderengine::Geometry> renderengine::Loader::createMultiPoint(const aiMesh * rawMesh)
{
    return std::shared_ptr<Geometry>();
}

std::shared_ptr<renderengine::Geometry> renderengine::Loader::createMultiLine(const aiMesh * rawMesh)
{
    return std::shared_ptr<Geometry>();
}
